package org.beanutopia.function;

import static org.beanutopia.function.CompositeFunction.self;
import com.google.common.base.Function;
import static com.google.common.base.Functions.constant;
import com.google.common.base.Nullable;
import com.google.common.base.Predicate;
import static com.google.common.base.Predicates.isNull;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterators.find;

import java.lang.reflect.Array;
import java.util.Arrays;
import static java.util.Arrays.asList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
 * @author ymeymann
 * @since Sep 17, 2007 12:40:12 AM
 */
public class PropertyFunctions {

  //public static final Function<Object,String> to_string = ;

  public static final Function<Object,Integer> to_hash_code = new Function<Object,Integer> () {
    public Integer apply(@Nullable Object object) {
      return object == null ? 0 : object.hashCode();
    }
  };

//  public static final Function<Annotation,Class> annotation_to_type = new Function<Annotation,Class> () {
//    public Class apply(Annotation from) {
//      return from.annotationType();
//    }
//  };

  public static final Function<Object,Class> to_class = new Function<Object,Class> () {
    public Class apply(Object object) { return object.getClass(); }
  };

  private static final Function<Object, Integer> collection_size = new Function<Object, Integer>(){
    public Integer apply(Object c) { return ((Collection)c).size(); }
  };

  private static final Function<Object, Integer> map_size = new Function<Object, Integer>(){
    public Integer apply(Object m) { return ((Map)m).size(); }
  };

  private static final Function<Object, Integer> array_size = new Function<Object, Integer>(){
    public Integer apply(Object o) { return Array.getLength(o); }
  };

  public static Predicate<Object> isInstanceOf(Class<?> c) {
    return new SubtypePredicate<Object>(c);
  }

  public static <T> Predicate<T> isInstanceOfGeneric(Class<T> c) {
    return new SubtypePredicate<T>(c);
  }

  public static Predicate<Object> isSupertypeOf(Class<?> c) {
    return new SupertypePredicate<Object>(c);
  }


  public static final Function<Object, Integer> SIZE = new FunctionPolicy<Object,Integer>().
          addRule(isNull(),                       constant(0)).
          addRule(isInstanceOf(Collection.class), collection_size).
          addRule(isInstanceOf(Map.class),        map_size).
          addRule(asPredicate(self(to_class).reflect("array","is").cast(Boolean.class)),
                                                  array_size).
          addRule(constant(1)).reduce();

  public static <T> Predicate<T> asPredicate(final Function<T,Boolean> f) {
    return new Predicate<T> () {
      public boolean apply(T t) {
        Boolean res = f.apply(t);
        return res == null ? false : res;
      }
    };
  }

  public static <T> Function<T,T> ifNull(final T defaultValue) {
    return new Function<T,T>() {
      public T apply(@Nullable T value) {
        return value == null ? defaultValue : value;
      }
    };
  }

  public static <T> T matchFirst(Predicate<? super T> where, Iterable<T> from) {
    if (from == null) return null;
    return find(from.iterator(), where);
  }

  public static <T> T matchFirst(Predicate<? super T> where, T... from) {
    if (from == null) return null;
    return matchFirst(where, asList(from));
  }

  public static <T> void matchAll(Collection<? super T> result, Predicate<? super T> where, Iterable<T> from) {
    if (from == null) return;
    for (T that : from) {
      if (where.apply(that)) result.add(that); 
    }
  }

  public static <T> void matchAll(Collection<? super T> result, Predicate<? super T> where, T... from) {
    if (from != null) matchAll(result, where, Arrays.asList(from));
  }

  public static <T> Function<T[], Iterable<T>> toIterable(Class<T> cl) {
    return toIterable();
  }

  public static <T> Function<T[], Iterable<T>> toIterable() {
    return new Function<T[], Iterable<T>>() {
      public Iterable<T> apply(@Nullable T[] o) {
        if (o == null)
          return Collections.emptyList();
        else
          return Arrays.asList(o);
      }
    };
  }

  public static <T> Predicate<Iterable<T>> exists(final Predicate<T> pred) {
    return new Predicate<Iterable<T>>() {
      public boolean apply(Iterable<T> iterable) {
        return any(iterable, pred);
      }
    };
  }

}
